package net.jalbum.filters;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;

import se.datadosen.jalbum.Msg;

public class TextBox extends ResizableBox {
	/**
	 * 
	 */
	private static final long serialVersionUID = -3676185755730114344L;

	public final static int TEXT_ALIGN_LEFT = 0;
	public final static int TEXT_ALIGN_CENTER = 1;
	public final static int TEXT_ALIGN_RIGHT = 2;

	protected Color lineColor = Color.BLACK;

	protected String font = "Arial";
	protected Color textColor = Color.BLACK;
	protected int textStrength = 255;
	protected int watermarkStrength = 64;
	protected int textSize = 24;
	protected boolean italic = false;
	protected boolean bold = false;
	protected int alignment = TextBox.TEXT_ALIGN_LEFT;

	protected Color shadowColor = Color.BLACK;
	protected int shadowAngle = 45;
	protected int shadowDistance = 6;
	protected int shadowStrength = 128;
	protected boolean showShadow = false;

	protected boolean showWatermark = false;
	protected double lineSpacing = 0.0;

	// TODO:localize
	protected String text = Msg.getString(this, "filters.text.welcomeText");
	protected Vector<String> lines = new Vector<String>();
	protected String widestLine = "";

	public TextBox() {
		this(100, 100);
	}

	public TextBox(final int width, final int height, final ResizableObjectPanel parentPanel) {
		this(width, height);
		this.parentPanel = parentPanel;
	}

	public TextBox(final ResizableObjectPanel parentPanel) {
		this();
		this.parentPanel = parentPanel;
	}

	public TextBox(final int width, final int height) {
		super(width, height);
		this.X1 = 0.2;
		this.Y1 = 0.2;
		this.X2 = 0.6;
		this.Y2 = 0.6;
		this.initial = false;
		this.keepDimension = true;
		this.color = new Color(255, 255, 225, 127);
		this.repaintOnCursorChange = false;
		this.checkDimensions(true);
	}

	@Override
	protected void paintHandles(final Graphics g) {
	}

	@Override
	protected int checkState(final int x, final int y) {
		// check if the mouse is inside or outside

		// calculate the positions as pixel size
		final int intX1 = (int) (this.OutX1 * (this.width - 1));
		final int intX2 = (int) (this.OutX2 * (this.width - 1));
		final int intY1 = (int) (this.OutY1 * (this.height - 1));
		final int intY2 = (int) (this.OutY2 * (this.height - 1));

		if (x > intX1 && x < intX2 && y > intY1 && y < intY2) {
			return ResizableObject.MOVE;
		} else {
			return ResizableObject.MAKE;
		}
	}

	@Override
	protected void setMouseCursor(final MouseEvent arg0) {
		// set the mouse cursor according to the current position
		this.position = this.checkState(arg0.getX(), arg0.getY());

		if (this.position == ResizableObject.MAKE) {

			final Cursor old = this.parentPanel.getCursor();
			this.parentPanel.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
			if (old != this.parentPanel.getCursor()) {
				this.parentPanel.renderPreview();
			}

		} else {
			super.setMouseCursor(arg0);
		}
	}

	@Override
	public void paint(final Graphics g) {
		this.prepareTextDrawing(g, false);

		if (this.parentPanel.isShowOutline()) {
			g.setColor(this.color);

			// calculate the positions as pixel size
			final int intX1 = (int) (this.OutX1 * (this.width - 1));
			final int intX2 = (int) (this.OutX2 * (this.width - 1));
			final int intY1 = (int) (this.OutY1 * (this.height - 1));
			final int intY2 = (int) (this.OutY2 * (this.height - 1));

			// draw the rectangular outlines of the object
			g.setColor(this.lineColor);
			g.drawRect(intX1 - 4, intY1, intX2 - intX1 + 8, intY2 - intY1);
		}

		this.drawText(g);
	}

	public void paintSelected(final Graphics g) {
		this.prepareTextDrawing(g, false);

		if (this.parentPanel.isShowOutline()) {
			// calculate the positions as pixel size
			final int intX1 = (int) (this.OutX1 * (this.width - 1));
			final int intX2 = (int) (this.OutX2 * (this.width - 1));
			final int intY1 = (int) (this.OutY1 * (this.height - 1));
			final int intY2 = (int) (this.OutY2 * (this.height - 1));

			g.setColor(this.color);
			g.fillRect(intX1 - 4, intY1, intX2 - intX1 + 8, intY2 - intY1);
			g.setColor(this.lineColor);
			g.drawRect(intX1 - 4, intY1, intX2 - intX1 + 8, intY2 - intY1);
		}

		this.drawText(g);
	}

	public void prepareTextDrawing(final Graphics g, final boolean finalRender) {

		final Graphics2D g2d = (Graphics2D) g;
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		int fontSetting = 0;
		if (this.italic) {
			fontSetting = Font.ITALIC;
		}
		if (this.bold) {
			fontSetting = fontSetting | Font.BOLD;
		}

		int renderFontSize = this.textSize;
		StringTokenizer tokenizer;

		final int comparisonFontSize = 48;
		g.setFont(new Font(this.font, fontSetting, comparisonFontSize));
		tokenizer = new StringTokenizer(this.text, "\n");
		String line;
		this.widestLine = "";
		int widestWidth = 0;
		this.lines.clear();
		while (tokenizer.hasMoreTokens()) {
			line = tokenizer.nextToken();
			this.lines.add(line);
			if (g2d.getFontMetrics().stringWidth(line) > widestWidth) {
				this.widestLine = line;
				widestWidth = g2d.getFontMetrics().stringWidth(this.widestLine);
			}
		}
		if (widestWidth <= 0) {
			widestWidth = 1;
		}

		if (finalRender) {
			final int desiredWidth = Math.round((float) ((this.X2 - this.X1) * (this.width - 1)));
			renderFontSize = comparisonFontSize * desiredWidth / widestWidth;
		}
		g.setFont(new Font(this.font, fontSetting, renderFontSize));

		// painting the text row by row
		tokenizer = new StringTokenizer(this.text, "\n");
		int textWidth = 0;

		final Iterator<String> iterator = this.lines.iterator();
		while (iterator.hasNext()) {
			line = iterator.next();
			textWidth = Math.max(textWidth, g2d.getFontMetrics().stringWidth(line));
		}

		if (!finalRender) {
			final int textHeight = g2d.getFontMetrics().getHeight() * this.lines.size();
			this.X2 = this.X1 + textWidth / (double) (this.width - 1);
			this.Y2 = this.Y1 + textHeight / (double) (this.height - 1);
			this.checkDimensions(false);
		}
	}

	public void drawText(final Graphics g) {
		// temporary variables to not overwrite the existing once when applying the watermark filter
		Color shadowColor, textColor;
		int shadowDistance, shadowAngle, shadowStrength, textStrength;
		boolean showShadow;

		if (this.showWatermark) {
			shadowStrength = this.watermarkStrength;
			textStrength = this.watermarkStrength;
			shadowColor = new Color(0, 0, 0);
			textColor = new Color(255, 255, 255);
			shadowDistance = 2;
			shadowAngle = 45;
			showShadow = true;
		} else {
			// copy default values
			shadowColor = this.shadowColor;
			textStrength = this.textStrength;
			textColor = this.textColor;
			shadowDistance = this.shadowDistance;
			shadowAngle = this.shadowAngle;
			showShadow = this.showShadow;
			shadowStrength = this.shadowStrength;
		}

		// calculate relative shadow distance depending on the image size
		final int relativeShadowDistance = g.getFont().getSize() * shadowDistance / this.textSize;
		final int shadownOffsetX = (int) (relativeShadowDistance * Math.cos(Math.toRadians(shadowAngle)));
		final int shadownOffsetY = (int) (relativeShadowDistance * Math.sin(Math.toRadians(shadowAngle)));

		final int intX1 = (int) (this.OutX1 * (this.width - 1));
		final int intY1 = (int) (this.OutY1 * (this.height - 1));

		int rowCounter = 0;
		int positionX;
		int positionY;
		String line;
		final Iterator<String> iterator = this.lines.iterator();
		while (iterator.hasNext()) {
			line = iterator.next();
			final int maxWidth = g.getFontMetrics().stringWidth(this.widestLine);
			if (this.alignment == TextBox.TEXT_ALIGN_RIGHT) {
				positionX = intX1 + maxWidth - g.getFontMetrics().stringWidth(line);
			} else if (this.alignment == TextBox.TEXT_ALIGN_CENTER) {
				positionX = intX1 + (maxWidth - g.getFontMetrics().stringWidth(line)) / 2;
			} else {
				// align == TEXT_ALIGN_LEFT
				positionX = intX1;
			}
			positionY = intY1 + g.getFontMetrics().getHeight() * rowCounter + g.getFontMetrics().getAscent();

			if (showShadow) {
				g.setColor(new Color(shadowColor.getRed(), shadowColor.getGreen(), shadowColor.getBlue(), shadowStrength));
				g.drawString(line, positionX + shadownOffsetX, positionY + shadownOffsetY);
			}

			g.setColor(new Color(textColor.getRed(), textColor.getGreen(), textColor.getBlue(), textStrength));
			g.drawString(line, positionX, positionY);
			rowCounter++;
		}
	}

	@Override
	public void update(final double x1, final double y1, final double x2, final double y2) {
		this.X1 = x1;
		this.Y1 = y1;
	}

	@Override
	protected void checkDimensions(final boolean changeOriginal) {
		// make sure that the lowest coordinates are always the left/top ones
		if (this.X1 > this.X2) {
			this.OutX2 = this.X1;
			this.OutX1 = this.X2;
		} else {
			this.OutX1 = this.X1;
			this.OutX2 = this.X2;
		}

		if (this.Y1 > this.Y2) {
			this.OutY2 = this.Y1;
			this.OutY1 = this.Y2;
		} else {
			this.OutY1 = this.Y1;
			this.OutY2 = this.Y2;
		}

		// check dimensions
		if (this.OutX1 < 0) {
			this.OutX1 = 0;
		}
		if (this.OutX1 > 1) {
			this.OutX1 = 1;
		}
		if (this.OutY1 < 0) {
			this.OutY1 = 0;
		}
		if (this.OutY1 > 1) {
			this.OutY1 = 1;
		}

		// is called when the mouse is released. In order to ensure smooth drag and drop for resizing the (possibly wrong positioned) coordinates are needed
		if (changeOriginal) {
			this.X1 = this.OutX1;
			this.X2 = this.OutX2;
			this.Y1 = this.OutY1;
			this.Y2 = this.OutY2;
		}
	}

	@Override
	protected void changeMousePosition(final MouseEvent arg0) {
		// a method that is called by dragMouse and releaseMouse to draw a current image of the box if it is moved or resized

		// calculate the mouse input for the zoomed image
		final int inputX = arg0.getX();
		final int inputY = arg0.getY();

		if (this.state == ResizableObject.MOVE) {
			// checking the boundaries for x and y individually to ensure smooth moving
			if ((!(this.X1 - this.OffsetX + inputX / (double) (this.width - 1) < 0 || this.X2 - this.OffsetX + inputX / (double) (this.width - 1) > 1) && (this.keepDimension || this.keepRatio || this.keepRatioKey))
					|| (!this.keepDimension && !this.keepRatio && !this.keepRatioKey)) {
				// if all X-coordinates remain within the image while one of the "keep-Options" is enabled or if non of the "keep-Options" is enabled, the coordinates are just set to the new value. if they overlap the image borders, they are corrected later by checkDimensions
				this.X1 = this.X1 - this.OffsetX + inputX / (double) (this.width - 1);
				this.X2 = this.X2 - this.OffsetX + inputX / (double) (this.width - 1);
				this.OffsetX = inputX / (double) (this.width - 1);
			} else {
				// if one of the Lock options is enabled and the coordinates move out of the image area, the box stops as soon as it hits one of the walls
				double correctionX = 0;
				if (this.X1 - this.OffsetX + inputX / (double) (this.width - 1) < 0) {
					correctionX = this.X1;
					this.X1 = 0;
					this.X2 = this.X2 - correctionX;
				} else if (this.X2 - this.OffsetX + inputX / (double) (this.width - 1) > 1) {
					correctionX = 1 - this.X2;
					this.X2 = 1;
					this.X1 = this.X1 + correctionX;
				}
			}
			// if all Y-coordinates remain within the image while one of the "keep-Options" is enabled or if non of the "keep-Options" is enabled, the coordinates are just set to the new value. if they overlap the image borders, they are corrected later by checkDimensions
			// see the corresponding part for X coordinates for further comments
			if ((!(this.Y1 - this.OffsetY + inputY / (double) (this.height - 1) < 0 || this.Y2 - this.OffsetY + inputY / (double) (this.height - 1) > 1) && (this.keepDimension || this.keepRatio || this.keepRatioKey))
					|| (!this.keepDimension && !this.keepRatio && !this.keepRatioKey)) {
				this.Y1 = this.Y1 - this.OffsetY + inputY / (double) (this.height - 1);
				this.Y2 = this.Y2 - this.OffsetY + inputY / (double) (this.height - 1);
				this.OffsetY = inputY / (double) (this.height - 1);
			} else {
				double correctionY = 0;
				if (this.Y1 - this.OffsetY + inputY / (double) (this.height - 1) < 0) {
					correctionY = this.Y1;
					this.Y1 = 0;
					this.Y2 = this.Y2 - correctionY;
				} else if (this.Y2 - this.OffsetY + inputY / (double) (this.height - 1) > 1) {
					correctionY = 1 - this.Y2;
					this.Y2 = 1;
					this.Y1 = this.Y1 + correctionY;
				}
			}
		}
	}

	public String getFont() {
		return this.font;
	}

	public void setFont(final String font) {
		this.font = font;
	}

	public Color getTextColor() {
		return this.textColor;
	}

	public void setTextColor(final Color textColor) {
		this.textColor = textColor;
	}

	public int getTextStrength() {
		return this.textStrength;
	}

	public void setTextStrength(final int textStrength) {
		this.textStrength = textStrength;
	}

	public int getTextSize() {
		return this.textSize;
	}

	public void setTextSize(final int textSize) {
		this.textSize = textSize;
	}

	public boolean isItalic() {
		return this.italic;
	}

	public void setItalic(final boolean italic) {
		this.italic = italic;
	}

	public boolean isBold() {
		return this.bold;
	}

	public void setBold(final boolean bold) {
		this.bold = bold;
	}

	public Color getShadowColor() {
		return this.shadowColor;
	}

	public void setShadowColor(final Color shadowColor) {
		this.shadowColor = shadowColor;
	}

	public int getShadowAngle() {
		return this.shadowAngle;
	}

	public void setShadowAngle(final int shadowAngle) {
		this.shadowAngle = shadowAngle;
	}

	public int getShadowDistance() {
		return this.shadowDistance;
	}

	public void setShadowDistance(final int shadowDistance) {
		this.shadowDistance = shadowDistance;
	}

	public int getShadowStrength() {
		return this.shadowStrength;
	}

	public void setShadowStrength(final int shadowStrength) {
		this.shadowStrength = shadowStrength;
	}

	public boolean isShowShadow() {
		return this.showShadow;
	}

	public void setShowShadow(final boolean showShadow) {
		this.showShadow = showShadow;
	}

	public boolean isShowWatermark() {
		return this.showWatermark;
	}

	public void setShowWatermark(final boolean showWatermark) {
		this.showWatermark = showWatermark;
	}

	public String getText() {
		return this.text;
	}

	public void setText(final String text) {
		this.text = text;
	}

	public int getWatermarkStrength() {
		return this.watermarkStrength;
	}

	public void setWatermarkStrength(final int watermarkStrength) {
		this.watermarkStrength = watermarkStrength;
	}

	public Color getLineColor() {
		return this.lineColor;
	}

	public void setLineColor(final Color lineColor) {
		this.lineColor = lineColor;
	}

	public int getAlignment() {
		return this.alignment;
	}

	public void setAlignment(final int alignment) {
		this.alignment = alignment;
	}
}
